/**
 * Copyright (c) 2007-2008 Nearchos Paspallis
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * AspectSense
 *
 * Middleware for annotation-based, context-aware and adaptive services for
 * mobile and ubiquitous computing
 *
 * This project is an open-source effort, initiated by Nearchos Paspallis as
 * part of his PhD work at the University of Cyprus (http://www.ucy.ac.cy).
 *
 * More information about the project is available at "http://aspectsense.org".
 */

package org.aspectsense.framework;

import org.aspectsense.evaluators.*;
import org.aspectsense.metadata.ServiceMetadata;
import org.aspectsense.metadata.MetadataFactory;
import org.aspectsense.metadata.ComponentMetadata;
import org.aspectsense.context.*;
import org.aspectsense.context.events.ContextChangedEvent;
import org.aspectsense.coreservices.ContextService;

import java.util.Map;
import java.util.HashMap;
import java.util.logging.Logger;

/**
 * <p/>
 * Date: Sep 11, 2007<br/>
 * Time: 10:27:51 AM
 * </p>
 *
 * @author Nearchos Paspallis [nearchos@cs.ucy.ac.cy]
 */
class Repository
{
    static public final Query COMPONENT_AVAILABILITY_QUERY_TYPE = new DefaultQuery(
            QueryType.CONTEXT_UPDATE, 
            DefaultContextType.COMPONENT_AVAILABILITY_CONTEXT_TYPE);

    private final Logger logger = Logger.getLogger(Repository.class.getCanonicalName());

    // ----------------- Singleton pattern implementation ------------------- //

    private static Repository instance = null;

    /**
     * Provides access to the lazy-loaded singleton instance of the
     * {@link Repository}.
     *
     * @return an instance of {@link Repository}
     */
    static Repository getInstance()
    {
        synchronized(Repository.class)
        {
            if(instance == null) instance = new Repository();

            return instance;
        }
    }

    private Repository()
    {
        logger.fine("Starting instance of " + Repository.class.getCanonicalName());
    }

    // --------------- End of singleton pattern implementation -------------- //

    // ---- Component installantion, uninstallation and update management --- //

    /** Mapping component IDs (class as string) to component container pools */
    private final Map<String, ComponentContainerPool> installedComponents
            = new HashMap<String, ComponentContainerPool>();

    /** Mapping service IDs (class as string) to service provider structures */
    private final Map<String, ServiceProviders> installedServices
            = new HashMap<String, ServiceProviders>();

    /** Mapping service IDs to the corresponding utility evaluator */
    private final Evaluators evaluators
            = DefaultEvaluators.getInstance();

    private static final ContextService contextService
            = CoreServices.getContextService();

    void installComponent(final Class componentType)
            throws InstallationException
    {
        if(componentType == null) throw new NullPointerException("Invalid null argument: " + Class.class);

        // check if given component type is valid
        if(!MetadataFactory.isComponent(componentType))
        {
            throw new IllegalArgumentException("The given type is not a component: " + componentType);
        }

        final String componentID = MetadataFactory.getComponentTypeCanonicalName(componentType);

        if(installedComponents.containsKey(componentID))
        {
            final String errorMessage = "Component type: " + componentType + " is already installed in the repository!";
            logger.severe(errorMessage);
            throw new RuntimeException(errorMessage);
        }

        final ComponentMetadata componentMetadata
                = MetadataFactory.getComponentMetadata(componentType);

        final Class providedServiceType = MetadataFactory.getCachedProvidedService(componentType);

        // register the component
        final ComponentContainerPool componentContainerPool
                = new ComponentContainerPool(componentType, providedServiceType);

        installedComponents.put(componentID, componentContainerPool);

        // update the provided services data-structure accordingly
        {
            final String providedServiceID = MetadataFactory.getServiceTypeCanonicalName(providedServiceType);
            ServiceProviders serviceProviders = this.installedServices.get(providedServiceID);

            // if no provider is available for this service type, then create
            // a new installedServices structure and put it to the map
            if(serviceProviders == null)
            {
                serviceProviders = new ServiceProviders(providedServiceType);
                this.installedServices.put(providedServiceID, serviceProviders);
            }

            serviceProviders.addServiceProvider(componentID);
        }

        // finally register the utility evaluator for this component
        try
        {
            evaluators.addServiceProvider(componentMetadata, componentType);
        }
        catch (ParserException pe)
        {
            throw new InstallationException("Error while installing component "
                    + "of type: " + componentType, pe);
        }

        // raise a relevant context event todo update?
        final Map<ContextType, ContextValue> map
                = new HashMap<ContextType, ContextValue>();
        final ContextValue contextValue = new DefaultStringContextValue(componentID);
        map.put(DefaultContextType.COMPONENT_AVAILABILITY_CONTEXT_TYPE, contextValue);
        final QueryResults queryResults = new QueryResults(map);

        contextService.raiseEvent(new ContextChangedEvent(
                this,
                COMPONENT_AVAILABILITY_QUERY_TYPE,
                queryResults));
    }

    public void uninstallComponent(final String componentID)
            throws InstallationException
    {
        if(componentID == null) throw new NullPointerException("Invalid null argument: " + componentID);

        if(!installedComponents.containsKey(componentID))
        {
            final String errorMessage = "Component: " + componentID + " is not installed in the repository!";
            logger.severe(errorMessage);
            throw new RuntimeException(errorMessage);
        }

        // register the component
        final ComponentContainerPool componentContainerPool = installedComponents.remove(componentID);

        // update the installed services
        final Class componentType
                = installedComponents.get(componentID).getComponentType();

        final Class providedServiceType
                = MetadataFactory.getCachedProvidedService(componentType);

        final String providedServiceID = MetadataFactory.getServiceTypeCanonicalName(providedServiceType);
        ServiceProviders serviceProviders = installedServices.get(providedServiceID);

        // if no provider is available for this service type, then
        // something went wrong!
        if(serviceProviders == null)
        {
            throw new RuntimeException("A service provider for service: " + providedServiceID
                    + " is not already available!");
        }

        serviceProviders.removeServiceProvider(componentID);

        // finally unregister the utility evaluator for this component
        evaluators.removeServiceProvider(componentContainerPool.getComponentMetadata());

        // raise a relevant context event todo update?
        final Map<ContextType, ContextValue> map
                = new HashMap<ContextType, ContextValue>();
        final ContextValue contextValue = new DefaultStringContextValue(componentID);
        map.put(DefaultContextType.COMPONENT_AVAILABILITY_CONTEXT_TYPE, contextValue);
        final QueryResults queryResults = new QueryResults(map);

        contextService.raiseEvent(new ContextChangedEvent(
                this,
                COMPONENT_AVAILABILITY_QUERY_TYPE,
                queryResults));
    }

    public void updateComponent(final String componentID, final Class componentType)
            throws InstallationException
    {
        if(componentID == null || componentType == null)
        {
            throw new NullPointerException("Illegal null argument");
        }

        // raise a relevant context event todo update?
        final Map<ContextType, ContextValue> map
                = new HashMap<ContextType, ContextValue>();
        final ContextValue contextValue = new DefaultStringContextValue(componentID);
        map.put(DefaultContextType.COMPONENT_AVAILABILITY_CONTEXT_TYPE, contextValue);
        final QueryResults queryResults = new QueryResults(map);

        contextService.raiseEvent(new ContextChangedEvent(
                this,
                COMPONENT_AVAILABILITY_QUERY_TYPE,
                queryResults));

        // todo implement!
        throw new UnsupportedOperationException();
    }

    // End of Component installantion, uninstallation and update management - //

    // ---------------------------- Help methods ---------------------------- //

    String [] getInstalledComponentIDs()
    {
        return installedComponents.keySet().toArray(new String [installedComponents.size()]);
    }

    String [] getInstalledServiceIDs()
    {
        return installedServices.keySet().toArray(new String [installedServices.size()]);
    }

    ServiceMetadata getServiceMetadata(final String serviceID, final String providerComponentID)
    {
        if(serviceID == null || providerComponentID == null)
        {
            throw new NullPointerException("Illegal null argument");
        }

        if(!installedServices.containsKey(serviceID))
        {
            throw new IllegalArgumentException("The specified serviceID is not installed: " + serviceID);
        }

        return installedServices.get(serviceID).getServiceMetadata(providerComponentID);
    }

    Class getComponentType(final String componentID)
    {
        if(componentID == null)
        {
            throw new NullPointerException("Illegal null argument");
        }

        return installedComponents.get(componentID).getComponentType();
    }

    Class getServiceType(final String serviceID)
    {
        return installedServices.get(serviceID).getServiceType();
    }

    String [] getServiceProviders(final String serviceID)
    {
        return installedServices.get(serviceID).getServiceProviderIDs();
    }

    // todo move?
    UtilityEvaluator getServiceUtilityEvaluator(final String serviceID, final String componentID)
    {
        return evaluators.getUtilityEvaluator(serviceID, componentID);
    }

    // todo move?
    double getServiceUtility(String serviceID, String providerComponentID)
            throws EvaluationException
    {
        final UtilityEvaluator utilityEvaluator
                = evaluators.getUtilityEvaluator(serviceID, providerComponentID);

        return utilityEvaluator.evaluateDouble();
    }

    // ------------------------- End of help methods ------------------------ //

    // --------------------------- Access methods --------------------------- //

    ComponentContainerPool getComponentContainerPool(final String componentID)
    {
        if(componentID == null)
        {
            throw new NullPointerException("Illegal null argument");
        }

        return installedComponents.get(componentID);
    }

    // ------------------------ End of access methods ----------------------- //
}